package com.xuecheng.media.service.jobhandler;


import cn.hutool.core.bean.BeanUtil;
import com.xuecheng.media.mapper.MediaFilesMapper;
import com.xuecheng.media.model.po.MediaFiles;
import com.xuecheng.media.model.po.MediaProcess;
import com.xuecheng.media.service.impl.MediaProcessServiceImpl;
import com.xuecheng.utils.Mp4VideoUtil;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import io.minio.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.*;

@Component
@Slf4j
@RequiredArgsConstructor
public class VideoTask {
    @Value("${videoprocess.ffmpegpath}")
    private String ffmpegpath;

    private final MediaProcessServiceImpl mediaProcessService;
    private final MinioClient minioClient;
    private final MediaFilesMapper mediaFilesMapper;
    private String bucket_name="video";

    /**
     * 采取分片广播
     */
    @XxlJob("videoJobHandler")
    public void videoJobHandler() throws InterruptedException {
        // 分片参数
        int shardIndex = XxlJobHelper.getShardIndex();
        int shardTotal = XxlJobHelper.getShardTotal();

        log.info("分片参数：当前分片序号 = {}, 总分片数 = {}", shardIndex, shardTotal);
        log.info("开始执行第" + shardIndex + "批任务");
        List<MediaProcess> mediaProcessList = null;
        int size=0;
        try{
            //用于获取Java虚拟机可用的处理器数量。
            int processors  = Runtime.getRuntime().availableProcessors();
            //一次处理的视频数量不要超过cpu核心数
            // 1.获取待处理的数据
            mediaProcessList=mediaProcessService.getMediaProcessList(shardIndex,shardTotal,processors);
            size=mediaProcessList.size();
            log.info("取出待处理的视频数：{}",size);
            if(size<=0){
                return;
            }
        }catch (Exception e){
            e.printStackTrace();
            return;
        }
        //创建一个可重用固定数量线程的线程池。这种线程池对于需要处理多个短期异步任务的程序来说，性能通常会更好。
        ExecutorService executorService = Executors.newFixedThreadPool(size);
        //计数器
        CountDownLatch countDownLatch = new CountDownLatch(size);
        //executorService.execute(()将处理任务加入线程池
        mediaProcessList.forEach(mediaProcess -> executorService.execute(()->{
            MediaFiles mediaFiles = new MediaFiles();
            BeanUtil.copyProperties(mediaProcess,mediaFiles);
            //1.1加锁
            int i = mediaProcessService.startTask(mediaProcess.getId());
            if(i<=0){
                log.info("基于数据库加锁失败：{}",mediaProcess);
                return;
            }
            //文件路径
            String filePath = mediaProcess.getFilePath();
            String fileId = mediaProcess.getFileId();
            //临时文件
            File file = null;

            try {
                //2.从minio中下载视频
                log.info("下载文件~~~~");
                GetObjectArgs object = GetObjectArgs.builder().bucket(bucket_name).object(filePath).build();
                GetObjectResponse getObjectResponse = minioClient.getObject(object);
                //创建临时文件
                file = File.createTempFile("minio", ".merge");
                //创建输入流
                FileOutputStream fileOutputStream = new FileOutputStream(file);
                //拷贝到历史文件
                IOUtils.copy(getObjectResponse, fileOutputStream);
                fileOutputStream.close();
            } catch (Exception e) {
                log.debug("下载文件失败");
                mediaProcessService.saveProcessFinishStatus(mediaProcess.getId(), "3", fileId, mediaProcess.getUrl(), "下载文件失败");
                return;
            }
            //3.转换格式
            log.info("转换文件格式~~~");
            //原文件名
            String filename = mediaProcess.getFilename();
            //新文件名
            String newFileName = filename.substring(0, filename.lastIndexOf(".")).concat(".mp4");
            //新文件
            File newFile =null;
            try {
                newFile  = File.createTempFile("minio", ".mp4");
                //getAbsolutePath--绝对路径，Path--相对路径
                //创建工具类对象
                Mp4VideoUtil videoUtil = new Mp4VideoUtil(ffmpegpath,file.getAbsolutePath(),newFileName,newFile.getAbsolutePath());
                //开始视频转换，成功将返回success
                String s = videoUtil.generateMp4();
                if(!s.equals("success")){
                    //记录错误信息
                    log.error("处理视频失败,视频地址:{},错误信息:{}", bucket_name + filePath, s);
                    mediaProcessService.saveProcessFinishStatus(mediaProcess.getId(), "3", fileId, null, s);
                }
            } catch (IOException e) {
                e.printStackTrace();
                log.info("转换文件格式错误：{}",mediaProcess);
                mediaProcessService.saveProcessFinishStatus(mediaProcess.getId(), "3", fileId, mediaProcess.getUrl(), "转换文件格式失败");
                return;
            }
            //4.删除并上传新格式视频
            RemoveObjectArgs removeObjectArgs = RemoveObjectArgs.builder()
                    .bucket(bucket_name)
                    .object(filePath)
                    .build();
            try {
                minioClient.removeObject(removeObjectArgs);
            } catch (Exception e) {
                e.printStackTrace();
                mediaProcessService.saveProcessFinishStatus(mediaProcess.getId(), "3", mediaProcess.getFileId(), mediaProcess.getUrl(), "删除旧格式视频失败");
                return;
            }

            //文件地址
            String newFilePath = getFilePathByMd5(mediaFiles.getFileId(),".mp4");
            try {
                UploadObjectArgs uploadObjectArgs = UploadObjectArgs.builder()
                        .bucket(bucket_name)
                        .object(newFilePath)
                        .filename(newFile.getAbsolutePath())
                        .build();
                minioClient.uploadObject(uploadObjectArgs);
            } catch (Exception e) {
                log.info("上传新文件失败:{}",mediaProcess);
                mediaProcessService.saveProcessFinishStatus(mediaProcess.getId(), "3", fileId, null, "处理后视频上传或入库失败");
                return;
            }finally {
                countDownLatch.countDown();
            }
            //更新mediafile
            mediaFiles.setFilePath(newFilePath);
            mediaFiles.setUrl(newFilePath);
            //修改fileName
            mediaFiles.setFilename(newFileName);
            mediaFilesMapper.updateById(mediaFiles);
            file.delete();
            newFile.delete();
            //更新
            mediaProcessService.saveProcessFinishStatus(mediaProcess.getId(), "4", fileId, mediaProcess.getUrl(), null);
        }));
        //等待,给一个充裕的超时时间,防止无限等待，到达超时时间还没有处理完成则结束任务
        countDownLatch.await(30, TimeUnit.MINUTES);

        //5.删除处理失败的分块文件

        //6.报错任务结果
    }

    //获取文件的md5加密
    private String getFileMd5(File file) {
        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            String fileMd5 = DigestUtils.md5Hex(fileInputStream);
            return fileMd5;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    //合并后的文件地址
    private String getFilePathByMd5(String fileMd5, String fileExt) {
        return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + fileMd5 + fileExt;
    }

}
